package com.evil.account.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.evil.account.api.RemoteDepartmentService;
import com.evil.account.constant.AccountConstant;
import com.evil.account.mapper.DepartmentMapper;
import com.evil.account.pojo.entity.Department;
import com.evil.account.service.DepartmentService;
import com.evil.account.service.UserDepartmentService;
import com.evil.account.service.UserEnterpriseService;
import com.evil.common.account.dto.FindByNameAndEIdReqDTO;
import com.evil.common.account.dto.FindByNamesAndEIdReqDTO;
import com.evil.common.account.dto.department.*;
import com.evil.common.account.dto.user.department.DepartmentManagerSettingReqDTO;
import com.evil.common.account.util.LoginUtil;
import com.evil.common.core.entity.PageModel;
import com.evil.common.core.enums.BaseEnum;
import com.evil.common.core.enums.SwitchEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.core.util.ClassifyUtil;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.family.dto.FindByIdsReqDTO;
import com.evil.common.redis.util.RedisUtil;
import com.evil.common.rocketmq.annotation.MqCache;
import com.evil.common.rocketmq.constant.MQConstant;
import com.evil.common.rocketmq.entity.MsgCache;
import com.evil.common.rocketmq.enums.EnterpriseMqOpTagEnum;
import com.evil.common.rocketmq.util.MqRedisKeyUtil;
import com.github.pagehelper.PageInfo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;

/**
 * 部门表
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@RestController
@AllArgsConstructor
public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Department> implements DepartmentService, RemoteDepartmentService {

    private final LoginUtil loginUtil;

    private final RedisUtil redisUtil;

    private final UserEnterpriseService userEnterpriseService;

    private final UserDepartmentService userDepartmentService;

    private final AccountLogServiceImpl logService;

    /**
     * 通过部门id查询部门基础信息
     *
     * @param departmentId departmentId
     * @return department
     */
    @Override
    public DepartmentBaseRespDTO findBaseById(Long departmentId) {
        return this.baseMapper.findBaseById(departmentId);
    }

    /**
     * 通过部门name查询部门基础信息
     *
     * @param findByNameAndEIdReqDTO findByNameAndEIdReqDTO
     * @return department
     */
    @Override
    public DepartmentBaseRespDTO findBaseByName(FindByNameAndEIdReqDTO findByNameAndEIdReqDTO) {
        return this.baseMapper.findBaseByName(findByNameAndEIdReqDTO);
    }

    /**
     * 通过部门ids查询部门基础信息
     *
     * @param findByIdsReqDTO findByIdsReqDTO
     * @return department
     */
    @Override
    public List<DepartmentBaseRespDTO> findBaseByIds(FindByIdsReqDTO findByIdsReqDTO) {
        if (findByIdsReqDTO.getCache()) {
            return this.baseMapper.findBaseByIds_Cache(findByIdsReqDTO.getIds(), findByIdsReqDTO.getFilterDeleted());
        } else {
            return this.baseMapper.findBaseByIds(findByIdsReqDTO.getIds(), findByIdsReqDTO.getFilterDeleted());
        }
    }

    /**
     * 通过部门names查询部门基础信息
     *
     * @param findByNamesAndEIdReqDTO findByNamesAndEIdReqDTO
     * @return department
     */
    @Override
    public List<DepartmentBaseRespDTO> findBaseByNames(FindByNamesAndEIdReqDTO findByNamesAndEIdReqDTO) {
        return this.baseMapper.findBaseByNames(findByNamesAndEIdReqDTO);
    }

    /**
     * 通过部门names查询部门基础信息
     *
     * @param findByNamesAndEIdReqDTO findByNamesAndEIdReqDTO
     * @return department
     */
    @Override
    public Map<String, DepartmentBaseRespDTO> findBaseMapByNames(FindByNamesAndEIdReqDTO findByNamesAndEIdReqDTO) {
        return StreamUtil.toOrderlyMapK(this.findBaseByNames(findByNamesAndEIdReqDTO), DepartmentBaseRespDTO::getDepartmentName);
    }

    /**
     * 通过部门ids查询部门基础信息
     *
     * @param findByIdsReqDTO findByIdsReqDTO
     * @return department
     */
    @Override
    public Map<Long, DepartmentBaseRespDTO> findBaseMapByIds(FindByIdsReqDTO findByIdsReqDTO) {
        return StreamUtil.toMapK(this.findBaseByIds(findByIdsReqDTO), DepartmentBaseRespDTO::getDepartmentId);
    }

    /**
     * 新增部门
     *
     * @param addDepartmentReqDTO addDepartmentReqDTO
     */
    @Override
    @MqCache
    @GlobalTransactional(rollbackFor = Exception.class)
    public void add(AddDepartmentReqDTO addDepartmentReqDTO) {
        // 检查企业管理员
        userEnterpriseService.checkEnterpriseManager(loginUtil.getLoginUserEnterprise());

        addDepartmentReqDTO.setEnterpriseId(loginUtil.getParamEnterpriseId(addDepartmentReqDTO.getEnterpriseId()));

        // 设置上级部门id、若为空则上级部门id为0
        if (null == addDepartmentReqDTO.getParentDepartmentId()) {
            addDepartmentReqDTO.setParentDepartmentId(AccountConstant.DEFAULT_PARENT_DEPARTMENT_ID);
        }
        Department department = BeanUtil.copyProperties(addDepartmentReqDTO, Department.class);
        logService.saveAndCheck(this.baseMapper, Department::getDepartmentId, department);

        // 设置部门主管
        DepartmentManagerSettingReqDTO departmentManagerSettingReqDTO = new DepartmentManagerSettingReqDTO();
        departmentManagerSettingReqDTO.setEnterpriseId(department.getEnterpriseId());
        departmentManagerSettingReqDTO.setDepartmentId(department.getDepartmentId());
        departmentManagerSettingReqDTO.setManagers(addDepartmentReqDTO.getManagers());
        userDepartmentService.departmentManagerSetting(departmentManagerSettingReqDTO);

        // 缓存MQ消息
        String key = MqRedisKeyUtil.requestUuidMqMsgCacheKey();
        String uuid = UUID.fastUUID().toString(true);
        String destination = EnterpriseMqOpTagEnum.ADD.topicAndTag(MQConstant.ENTERPRISE_DEPT_MQ_KEY);
        DepartmentBaseRespDTO base = BeanUtil.copyProperties(department, DepartmentBaseRespDTO.class);
        boolean lpush = redisUtil.lpush(key, new MsgCache(uuid, destination, base));
        log.info("dept add|enterprise:{}, dept:{}, uuid:{}, destination:{}, lpush:{}",
                base.getEnterpriseId(), base.getDepartmentId(), uuid, destination, lpush);
    }

    /**
     * 编辑部门
     *
     * @param modifyDepartmentReqDTO modifyDepartmentReqDTO
     */
    @Override
    @MqCache
    @GlobalTransactional(rollbackFor = Exception.class)
    public void modify(ModifyDepartmentReqDTO modifyDepartmentReqDTO) {
        // 检查企业管理员
        userEnterpriseService.checkEnterpriseManager(loginUtil.getLoginUserEnterprise());

        modifyDepartmentReqDTO.setEnterpriseId(loginUtil.getParamEnterpriseId(modifyDepartmentReqDTO.getEnterpriseId()));

        Department department = this.baseMapper.findById(modifyDepartmentReqDTO.getDepartmentId());
        // 检查企业
        loginUtil.checkEnterpriseId(department.getEnterpriseId());

        BeanUtil.copyProperties(modifyDepartmentReqDTO, department);
        logService.saveAndCheck(this.baseMapper, Department::getDepartmentId, department);

        // 设置部门主管
        DepartmentManagerSettingReqDTO departmentManagerSettingReqDTO = new DepartmentManagerSettingReqDTO();
        departmentManagerSettingReqDTO.setEnterpriseId(department.getEnterpriseId());
        departmentManagerSettingReqDTO.setDepartmentId(department.getDepartmentId());
        departmentManagerSettingReqDTO.setManagers(modifyDepartmentReqDTO.getManagers());
        userDepartmentService.departmentManagerSetting(departmentManagerSettingReqDTO);

        // 缓存MQ消息
        String key = MqRedisKeyUtil.requestUuidMqMsgCacheKey();
        String uuid = UUID.fastUUID().toString(true);
        String destination = EnterpriseMqOpTagEnum.MODIFY.topicAndTag(MQConstant.ENTERPRISE_DEPT_MQ_KEY);
        DepartmentBaseRespDTO base = BeanUtil.copyProperties(department, DepartmentBaseRespDTO.class);
        boolean lpush = redisUtil.lpush(key, new MsgCache(uuid, destination, base));
        log.info("dept modify|enterprise:{}, dept:{}, uuid:{}, destination:{}, lpush:{}",
                base.getEnterpriseId(), base.getDepartmentId(), uuid, destination, lpush);
    }

    /**
     * 获取部门分页列表
     *
     * @param pageModel pageModel
     * @return PageInfo
     */
    @Override
    public PageInfo<DepartmentPageRespDTO> page(PageModel<DepartmentPageReqDTO> pageModel) {
        DepartmentPageReqDTO param = pageModel.getParam();
        param.setEnterpriseId(loginUtil.getParamEnterpriseId(param.getEnterpriseId()));
        return this.baseMapper.departmentPage(pageModel);
    }

    /**
     * 删除部门
     *
     * @param deleteDepartmentReqDTO deleteDepartmentReqDTO
     */
    @Override
    @MqCache
    @GlobalTransactional(rollbackFor = Exception.class)
    public void delete(DeleteDepartmentReqDTO deleteDepartmentReqDTO) {
        // 检查企业管理员
        userEnterpriseService.checkEnterpriseManager(loginUtil.getLoginUserEnterprise());
        Department department = this.baseMapper.findById(deleteDepartmentReqDTO.getDepartmentId());
        // 检查企业
        loginUtil.checkEnterpriseId(department.getEnterpriseId());

        department.setIsDeleted(SwitchEnum.YES.getId());
        logService.saveAndCheck(this.baseMapper, Department::getDepartmentId, department);

        // 缓存MQ消息
        String key = MqRedisKeyUtil.requestUuidMqMsgCacheKey();
        String uuid = UUID.fastUUID().toString(true);
        String destination = EnterpriseMqOpTagEnum.DELETE.topicAndTag(MQConstant.ENTERPRISE_DEPT_MQ_KEY);
        DepartmentBaseRespDTO base = BeanUtil.copyProperties(department, DepartmentBaseRespDTO.class);
        boolean lpush = redisUtil.lpush(key, new MsgCache(uuid, destination, base));
        log.info("dept delete|enterprise:{}, dept:{}, uuid:{}, destination:{}, lpush:{}",
                base.getEnterpriseId(), base.getDepartmentId(), uuid, destination, lpush);
    }

    /**
     * 获取部门基础信息树
     *
     * @param departmentTreeReqDTO departmentBaseTreeReqDTO
     * @return PageInfo
     */
    @Override
    public List<DepartmentBaseTreeRespDTO> baseTree(DepartmentTreeReqDTO departmentTreeReqDTO) {
        departmentTreeReqDTO.setEnterpriseId(loginUtil.getParamEnterpriseId(departmentTreeReqDTO.getEnterpriseId()));
        Long root = AccountConstant.DEFAULT_PARENT_DEPARTMENT_ID;
        Collection<Department> tree;
        // 指定上级，然后逐级获取其子级
        if (null != departmentTreeReqDTO.getParentDepartmentId()) {
            Department parent = this.baseMapper.findById(departmentTreeReqDTO.getParentDepartmentId());

            tree = new ArrayList<>();
            this.recursionTree(tree, new HashSet<>(), ListUtil.toList(parent));
            root = departmentTreeReqDTO.getParentDepartmentId();
        }
        // 指定关键字，然后逐级获取其父级直到根
        else if (StringUtils.isNotBlank(departmentTreeReqDTO.getKeyword())) {
            List<Department> departments = this.baseMapper.findByTree(departmentTreeReqDTO);
            Map<Long, Department> departmentMap = new HashMap<>();
            this.recursionTree(departmentMap, departments);
            tree = departmentMap.values();
        }
        // 默认，获取企业全部部门
        else {
            tree = this.baseMapper.findByTree(departmentTreeReqDTO);
        }
        return ClassifyUtil.classify(
                StreamUtil.transListT(tree, d -> BeanUtil.copyProperties(d, DepartmentBaseTreeRespDTO.class)),
                DepartmentBaseTreeRespDTO::getDepartmentId,
                DepartmentBaseTreeRespDTO::getParentDepartmentId,
                Comparator.comparing(DepartmentBaseTreeRespDTO::getSort),
                DepartmentBaseTreeRespDTO::setChildren,
                root
        );
    }

    /**
     * 获取部门树
     *
     * @param departmentTreeReqDTO departmentBaseTreeReqDTO
     * @return PageInfo
     */
    @Override
    public List<DepartmentTreeRespDTO> tree(DepartmentTreeReqDTO departmentTreeReqDTO) {
        departmentTreeReqDTO.setEnterpriseId(loginUtil.getParamEnterpriseId(departmentTreeReqDTO.getEnterpriseId()));
        List<Department> departments = this.baseMapper.findByTree(departmentTreeReqDTO);
        List<DepartmentTreeRespDTO> departmentBases = StreamUtil.transListT(departments, d -> BeanUtil.copyProperties(d, DepartmentTreeRespDTO.class));
        return ClassifyUtil.classify(
                departmentBases,
                DepartmentTreeRespDTO::getDepartmentId,
                DepartmentTreeRespDTO::getParentDepartmentId,
                Comparator.comparing(DepartmentTreeRespDTO::getSort),
                DepartmentTreeRespDTO::setChildren,
                ObjectUtil.defaultIfNull(departmentTreeReqDTO.getParentDepartmentId(), AccountConstant.DEFAULT_PARENT_DEPARTMENT_ID)
        );
    }

    /**
     * 企业部门基础列表
     *
     * @param departmentBasesReqDTO departmentBasesReqDTO
     * @return DepartmentBaseRespDTO 列表
     */
    @Override
    public List<DepartmentBaseRespDTO> bases(DepartmentBasesReqDTO departmentBasesReqDTO) {
        // 参数中企业id不存在,则获取当前登录用户的企业id ; 存在则说明是总后台调用
        Long enterpriseId = loginUtil.getParamEnterpriseId(departmentBasesReqDTO.getEnterpriseId());
        return this.baseMapper.findBaseByEnterpriseId(enterpriseId);
    }

    /**
     * 指定上级，然后逐级获取其子级
     *
     * @param tree    结果树
     * @param ids     id记录防止死循环
     * @param parents 当前父级层级
     */
    private void recursionTree(Collection<Department> tree, HashSet<Long> ids, List<Department> parents) {
        // 防止死循环
        Set<Long> parentIds = StreamUtil.transSetT(parents, Department::getDepartmentId);
        if (StreamUtil.anyMatch(parentIds, e -> !ids.add(e))) {
            throw new BusinessException(BaseEnum.ERROR, "部门数据异常");
        }
        List<Department> children = this.baseMapper.findByParentIds(parentIds);
        // 递归
        if (CollectionUtil.isNotEmpty(children)) {
            tree.addAll(children);
            this.recursionTree(tree, ids, children);
        }
    }

    /**
     * 指定关键字，然后逐级获取其父级直到根
     *
     * @param departmentMap 结果集合
     * @param departments   当前层级
     */
    private void recursionTree(Map<Long, Department> departmentMap, List<Department> departments) {
        List<Long> unExistPIds = StreamUtil.transListTF(departments, Department::getParentDepartmentId, departmentMap::containsKey);
        // 递归找父级
        if (CollectionUtil.isNotEmpty(unExistPIds)) {
            List<Department> parents = this.baseMapper.findByIds(unExistPIds, true);
            departmentMap.putAll(StreamUtil.toOrderlyMapK(parents, Department::getDepartmentId));
            this.recursionTree(departmentMap, parents);
        }
    }
}
